package io.jsen.meta.diff.context;

import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import io.jsen.meta.diff.field.MetaField;
import io.jsen.meta.diff.field.MetaFieldDefine;
import io.jsen.meta.diff.obj.Convert;
import io.jsen.meta.diff.obj.MetaObject;
import io.jsen.meta.diff.obj.complex.MetaCollection;
import io.jsen.meta.diff.obj.complex.MetaComplex;
import io.jsen.meta.diff.obj.complex.MetaMap;
import io.jsen.meta.diff.obj.special.MetaDelete;
import io.jsen.meta.diff.obj.special.MetaNull;
import io.jsen.meta.diff.support.MetaDiffModel;
import io.jsen.meta.diff.support.MetaModifier;
import io.jsen.meta.diff.support.MetaObjectSupport;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

import static io.jsen.meta.diff.support.MetaChangeModifier.isNoChange;
import static io.jsen.meta.diff.support.MetaObjectSupport.setValue;

/**
 * 具体的diff逻辑
 * <p>
 * 考虑拆分
 *
 * @author jsen
 * @date 2021/4/6
 */
class MetaOpContext {

    /**
     * 创建一个diff对象
     *
     * @param metaDiffModel diff model include typeToken
     * @param <T>           object type
     * @return diff object
     */
    static <T> MetaObject<?, ?> toMeta(MetaDiffModel<T> metaDiffModel) {

        try {
            // 创建diff对象
            MetaObject<?, ?> newMeta = createMetaObject(new MetaField(MetaFieldDefineContext
                    .getClazzDefine(metaDiffModel.getToken())), metaDiffModel.getNewNativeObj());
            MetaObject<?, ?> oldMeta = createMetaObject(new MetaField(MetaFieldDefineContext
                    .getClazzDefine(metaDiffModel.getToken())), metaDiffModel.getOldNativeObj());

            // 执行diff
            if (newMeta instanceof MetaComplex &&
                    !StringUtils.equals(newMeta.getIndex(), oldMeta.getIndex())) {
                // 复杂对象 index 不同，直接插入
                return newMeta.doDiff(new MetaNull(
                        new MetaField(MetaFieldDefineContext.getClazzDefine(metaDiffModel.getToken()))));
            } else {
                return newMeta.doDiff(oldMeta);
            }
        } catch (IllegalAccessException | InstantiationException e) {
            throw new IllegalArgumentException("转换出错", e);
        }
    }

    @SuppressWarnings("UnstableApiUsage")
    static <T> MetaObject<?, ?> loadMeta(TypeToken<T> typeToken, T obj) {
        try {
            // 创建diff对象
            return createMetaObject(new MetaField(MetaFieldDefineContext
                    .getClazzDefine(typeToken)), obj);
        } catch (IllegalAccessException | InstantiationException e) {
            throw new IllegalArgumentException("转换出错", e);
        }
    }

    /**
     * meta转native
     *
     * @param metaObject metaObject
     * @return native object
     */
    static Object toNative(MetaObject<?, ?> metaObject) {
        ObjectRef ref = new ObjectRef(null);
        try {
            createNativeObject(ref, metaObject);
        } catch (IllegalAccessException | InstantiationException e) {
            throw new IllegalArgumentException("转换出错", e);
        }
        return ref.getObject();
    }


    private static void createNativeObject(ObjectRef parent, MetaObject<?, ?> metaObject)
            throws IllegalAccessException, InstantiationException {
        if (isNoChange(metaObject.getModifier()) &&
                !metaObject.getMetaRelation().getMetaField().isMetaUniqueKey()) {
            return;
        }

        MetaField metaField = metaObject.getMetaRelation().getMetaField();
        @SuppressWarnings("rawtypes")
        Convert convert = metaField.getNativeConvert();
        if (convert != null) {
            @SuppressWarnings("unchecked")
            Object o = convert.convert(metaField, metaObject);
            setValue(metaField, parent.getObject(), o);
            return;
        }

        if (MetaObjectConvertContext.supportMeta(metaObject)) {
            Object o = MetaObjectConvertContext.convert(metaField, metaObject);
            setValue(metaField, parent.getObject(), o);
            parent.setObject(o);
            return;
        }

        if (metaObject instanceof MetaNull) {
            return;
        }
        if (metaObject instanceof MetaDelete) {
            if (parent.getObject() instanceof Collection || parent.getObject() instanceof Map) {
                return;
            }
            metaField.getFieldDefine()
                    .setValue(parent.getObject(), MetaDeleteContext.getDeleteVal(metaField.getFieldDefine()));
            return;
        }


        // 类
        Collection<MetaObject<?, ?>> collection;
        int modifier = metaObject.getModifier();
        ObjectRef newParRef = new ObjectRef(MetaObjectSupport.newNativeObject(metaObject));
        if (MetaModifier.isComplex(modifier)) {
            collection = ((MetaComplex) metaObject).getVal().values();
        } else if (MetaModifier.isCollection(modifier)) {
            collection = ((MetaCollection) metaObject).getVal();
        } else if (MetaModifier.isMap(modifier)) {
            collection = ((MetaMap<?>) metaObject).getVal().values();
        } else {
            throw new IllegalArgumentException("不支持的类型转为native" + metaObject.getClass().getSimpleName());
        }
        setNativeCollection(metaField, parent, newParRef, collection);
    }

    private static void setNativeCollection(MetaField metaField, ObjectRef parent, ObjectRef newParRef, Collection<MetaObject<?, ?>> collection) throws InstantiationException, IllegalAccessException {
        for (MetaObject<?, ?> obj : collection) {
            createNativeObject(newParRef, obj);
        }
        setValue(metaField, parent.getObject(), newParRef.getObject());
        parent.setObject(newParRef.getObject());
    }


    @SuppressWarnings("all")
    private static MetaObject<?, ?> createMetaObject(MetaField metaField, Object obj)
            throws IllegalAccessException, InstantiationException {
        if (obj == null) {
            return new MetaNull(metaField);
        }
        // 有自定义转换器，使用自定义转换器
        Convert convert = metaField.getMetaConvert();
        if (convert != null) {
            return (MetaObject<?, ?>) convert.convert(metaField, obj);
        }

        if (MetaObjectConvertContext.supportNative(obj)) {
            // 有转换器，使用转换器
            if (MetaDeleteContext.isDelete(metaField.getFieldDefine(), obj)) {
                return new MetaDelete(metaField);
            }
            return MetaObjectConvertContext.convert(metaField, obj);
        } else if (obj instanceof Collection) {
            // Collection
            Collection<?> collection = (Collection<?>) obj;
            MetaFieldDefine valueFieldDefine = MetaFieldDefineContext.getClazzDefine(
                    TypeToken.of(metaField.getFieldDefine().getGenericTypeList().get(0)));
            List<MetaObject<?, ?>> valList = new ArrayList<>(collection.size());
            int i = 0;
            for (Object o : collection) {
                valList.add(createMetaObject(new MetaField(valueFieldDefine, i++), o));
            }
            metaField.setTrueClass(obj.getClass());
            return new MetaCollection(metaField, valList);
        } else if (obj instanceof Map) {
            // Map
            MetaFieldDefine valueFieldDefine = MetaFieldDefineContext.getClazzDefine(
                    TypeToken.of(metaField.getFieldDefine().getGenericTypeList().get(1)));
            Map<?, ?> map = (Map<?, ?>) obj;
            Map<? super Object, ? super MetaObject<?, ?>> valMap = new HashMap();
            for (Map.Entry entry : map.entrySet()) {
                valMap.put(entry.getKey(), createMetaObject(
                        new MetaField(valueFieldDefine, entry.getKey()), entry.getValue()));
            }
            metaField.setTrueClass(obj.getClass());
            return new MetaMap(metaField, valMap);
        } else {
            List<MetaFieldDefine> metaFieldList = MetaFieldDefineContext.getClazzFields(
                    metaField.getFieldDefine().getCurrentFieldTypeToken()).stream()
                    .filter(item -> !item.getIgnore()).collect(Collectors.toList());
            Map<MetaField, MetaObject<?, ?>> val = Maps.newHashMapWithExpectedSize(metaFieldList.size());
            for (MetaFieldDefine metaFieldDefine : metaFieldList) {
                Object fieldVal = metaFieldDefine.getValue(obj);
                if (MetaDeleteContext.isDelete(metaFieldDefine, fieldVal)) {
                    val.put(new MetaField(metaFieldDefine), new MetaDelete(new MetaField(metaFieldDefine)));
                } else {
                    val.put(new MetaField(metaFieldDefine), createMetaObject(new MetaField(metaFieldDefine),
                            fieldVal));
                }
            }
            metaField.setTrueClass(obj.getClass());
            return new MetaComplex(metaField, val);
        }
    }


    @Data
    private static class ObjectRef {
        private Object object;

        public ObjectRef(Object object) {
            this.object = object;
        }

        public void setObject(Object object) {
            if (this.object == null) {
                this.object = object;
            }
        }
    }

    private MetaOpContext() {
    }
}
